#include "stm32f10x.h"
#include "bsp_spi_flash.h"
#include <stdio.h>  

//这个得注意些，得把无关的头文件去除

uint32_t SPITime_out=0;   //设置超时时间

/**
*	@brief	SPI_FLASH初始化函数(1.初始化目标引脚及时钟 2.使能SPI时钟 3.配置SPI外设的相关参数 4.最终开启SPI外设)
*	@param	无
*	@retval 无
*/
void SPI_FLASH_Init(void)
{
    SPI_InitTypeDef 	SPI_InitStructure;    //SPI初始化结构体
    GPIO_InitTypeDef 	GPIO_InitStructure;   //GPIO初始化结构体

    //开启相关时钟    (把要变得都用宏先定义)
    FLASH_SPI_CLOCK_FUN	(FLASH_SPI_CLOCK_CMD,ENABLE);  //SPI时钟
    FLASH_SPI_CS_CLOCK_FUN(FLASH_SPI_CS_CLOCK_CMD|FLASH_SPI_SCK_CLOCK_CMD|FLASH_SPI_MISO_CLOCK_CMD|FLASH_SPI_MOSI_CLOCK_CMD,ENABLE);//开启SPI引脚时钟

    //初始化目标引脚
    //配置SPI的CS引脚,普通IO口即可(如果特征相同则用一个)
    GPIO_InitStructure.GPIO_Pin=FLASH_SPI_CS_PIN;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
    GPIO_Init(FLASH_SPI_CS_PORT,&GPIO_InitStructure);
    //配置SPI的SCK引脚,MISO引脚,MOSI引脚
    GPIO_InitStructure.GPIO_Pin=FLASH_SPI_SCK_PIN|FLASH_SPI_MISO_PIN|FLASH_SPI_MOSI_PIN;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP ;
    GPIO_Init(FLASH_SPI_SCK_PORT,&GPIO_InitStructure);

    //拉高CS引脚电平(片选失能)
    FLASH_SPI_CS_HIGH();

    //SPI配置模式  (这个得根据具体的FLASH芯片来配置)
    SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex; //两线全双工
    SPI_InitStructure.SPI_Mode=SPI_Mode_Master;   //主机模式
    SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;   //数据长度为8位
    SPI_InitStructure.SPI_CPOL=SPI_CPOL_High;         //1
    SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;        //1  即模式3
    SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;           //CS引脚由软件控制
    SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;   //2分频  SPI1挂在APB2总线上(2分频后即36MHz)
    SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;   //最高位先发
    SPI_InitStructure.SPI_CRCPolynomial=7;             //这个值其实无效
    SPI_Init(FLASH_SPIx,&SPI_InitStructure);           //

    //最后一步,使能SPI
    SPI_Cmd(FLASH_SPIx,ENABLE);
}

/**
 *  @brief  :打印错误码
 *  @param  :错误码
 *  @retval :返回值,失败返回0
 */
static	uint32_t	SPI_TIMEOUT_UserCallback(uint8_t	errorCode)
{
    //这里肯定用到了USART串口              //要包含那个stdio.h吗肯定呀
    //EEPROM_ERROR("I2C出错 errorCode=%d",errorCode);
    SPI_DEBUG("SPI出错 errorCode=%d",errorCode);
    return	0;     //失败返回0
}

//一般流程是写一个字节,等待一定的时序,然后读，之后写。。。最后读

/**
 *   @brief	使用SPI发送一个字节的数据
 *	 @param	byte要发送的数据
 *	 @retval 返回收到的数据
 */
uint8_t	SPI_FLASH_SendByte(uint8_t byte)  //写一个同时读一个(突出的是写)
{
    SPITime_out=SPI_FLAG_TIMEOUT;   //设置超时时间
    //写之前得确保发送缓冲为空
    while(SPI_I2S_GetFlagStatus(FLASH_SPIx,SPI_I2S_FLAG_TXE)==RESET)
    {
        //但又不能一直在此等(一定次数后得退出)
        if((SPITime_out--)==0)
        {
            return SPI_TIMEOUT_UserCallback(0);
        }
    }
    //空了之后,将要发送的数据写入缓冲区
    SPI_I2S_SendData(FLASH_SPIx,byte);

    //接下来就该读了
    SPITime_out=SPI_FLAG_TIMEOUT;
    //等待接收缓冲区非空,RXNE事件
    while(SPI_I2S_GetFlagStatus(FLASH_SPIx,SPI_I2S_FLAG_RXNE)==RESET)
    {
        if((SPITime_out--)==0)
        {
            return SPI_TIMEOUT_UserCallback(1);
        }
    }
    //读取数据寄存器,获取接收缓冲区数据
    return	SPI_I2S_ReceiveData(FLASH_SPIx);
}

/*
 * @brief  使用SPI读取一个字节的数据
 * @param  无
 * @retval 返回接收到的数据
 */
uint8_t SPI_FLASH_ReadByte(void)           //主要是读
{
    return	(SPI_FLASH_SendByte(Dummy_Byte));
}

/**
 *@brief   读取FLASH ID
 *@param   无
 *@retval  FLASH ID
 */
uint32_t SPI_FLASH_ReadJedecDeviceID(void)
{
    uint32_t Temp=0;
    uint32_t Temp0=0;
    uint32_t Temp1=0;
    uint32_t Temp2=0;

    //开始通讯:CS引脚置低电平(低电平即片选,一切从这里开始)
    FLASH_SPI_CS_LOW();
    //发送相关指令(这里即发送Jedec指令，读取ID)
    SPI_FLASH_SendByte(W25X_JedecDeviceID);
    //接下来读取返回的数据
    Temp0=SPI_FLASH_ReadByte();   //返回生产厂商
    Temp1=SPI_FLASH_ReadByte();   //返回存储器类型
    Temp2=SPI_FLASH_ReadByte();   //返回容量
    //停止通讯:拉高CS引脚
    FLASH_SPI_CS_HIGH();
    //把数据合并起来
    Temp=(Temp0<<16)|(Temp1<<8)|(Temp2);
    return Temp;
}

/**
 *  @brief 读取DeviceID
 *  @param 无
 *  @retval DeviceID
 */
uint32_t SPI_FLASH_ReadDeviceID(void)
{
	uint8_t temp;
    //因为ReadByte和WriteByte还不是一个整体,故相应的起始信号得加上
    FLASH_SPI_CS_LOW();   //有拉低就有对应的拉高
    SPI_FLASH_SendByte(W25X_DeviceID);
    SPI_FLASH_ReadByte();   //
    SPI_FLASH_ReadByte();
    SPI_FLASH_ReadByte();   //前三字节都是dummy(虚字节)
    temp=SPI_FLASH_ReadByte();    //这样写对吗?
	FLASH_SPI_CS_HIGH();
	
	return temp;
}

/**
 * @brief   向FLASH发送写使能命令
 * @param 	none
 * @retval  none    (SPI要大写)
 */
void SPI_FLASH_WriteEnable(void)   //只要写就要用到它
{
    //SPI通讯开始:片选拉低
    FLASH_SPI_CS_LOW();
    //发送写使能命令
    SPI_FLASH_SendByte(W25X_WriteEnable);
    //通讯结束:片选拉高
    FLASH_SPI_CS_HIGH();
}

/**
 * @brief	等待WIP(BUSY)标志位被置0,即等待FLASH内的数据写入完毕
 * @param  none
 * @retval none
 */
void	SPI_FLASH_WaitForWriteEnd(void)   //一般连续大批量的读写之后会用到此函数
{
    uint8_t FLASH_Status=0;

    //每次写之前都得拉低片选
    FLASH_SPI_CS_LOW();
    //发送读状态寄存器命令
    SPI_FLASH_SendByte(W25X_ReadStatusReg);
    //等待FLASH不忙
    do
    {
        FLASH_Status=SPI_FLASH_ReadByte();
    }
    while((FLASH_Status&WIP_Flag)==0x01);   //WIP(Work In Process)
    //停止拉高CS
    FLASH_SPI_CS_HIGH();
}

/**
 *  @brief 擦除FLASH扇区
 *  @param SectorAddr;要擦除的扇区地址(这个地址要注意4KB对齐)
 *  @retval 无
 */
void	SPI_FLASH_SectorErase(uint32_t SectorAddr)   //擦除实质就是写
{
    //既然是写就要发送写使能
    SPI_FLASH_WriteEnable();
    //等待发送完毕
    SPI_FLASH_WaitForWriteEnd();    //这个可以不要吗
    //扇区擦除(高字节先发,高位先发)
    FLASH_SPI_CS_LOW();
    //发送扇区擦除指令
    SPI_FLASH_SendByte(W25X_SectorErase);
    //发送高字节
    SPI_FLASH_SendByte((SectorAddr&0xFF0000)>>16);
    //发送中字节
    SPI_FLASH_SendByte((SectorAddr&0x00FF00)>>8);
    //发送低字节
    SPI_FLASH_SendByte((SectorAddr&0x0000FF));
    //停止信号
    FLASH_SPI_CS_HIGH();
    //等待完成
    SPI_FLASH_WaitForWriteEnd();
}

/**
 * @brief 对FLASH按页写入数据,调用本函数需提前擦除扇区
 * @param pBuffer,要写入数据的指针
 * @param WriteAddr,要写入的地址(24bit)
 * @param NumByteToWrite,写入数据的长度,必须小于等于页大小
 * @retval 无
 */
void	SPI_FLASH_PageWrite(uint8_t *pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
    //发送FLASH写使能命令
    SPI_FLASH_WriteEnable();  //这里已经包含了完整的时许
    //拉低CS管脚
    FLASH_SPI_CS_LOW();
    //写相应的指令(页写)
    SPI_FLASH_SendByte(W25X_PageProgram);
    //发送写地址的高字节
    SPI_FLASH_SendByte((WriteAddr & 0x00FF0000)>>16);
    //发送地址的中字节
    SPI_FLASH_SendByte((WriteAddr & 0x0000FF00)>>8);
    //发送地址的低字节
    SPI_FLASH_SendByte((WriteAddr & 0x000000FF));

    if(NumByteToWrite > SPI_FLASH_PerWritePageSize)
    {
        //超过256字节按256字节搞
        NumByteToWrite=SPI_FLASH_PerWritePageSize;
        SPI_ERROR("SPI_FLASH_PageWrite too large !");
    }
    //写数据
    while(NumByteToWrite--)   //循环写入NumByteToWrite个字节
    {
        //发送当前要写入的数据字节
        SPI_FLASH_SendByte(*pBuffer++);
    }
    //写入完毕后,发送停止信号
    FLASH_SPI_CS_HIGH();
    //等待FLASH写入完毕
    SPI_FLASH_WaitForWriteEnd();
}

/**
 *	@brief 对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
 * @param  pBuffer,要写入数据的指针
 * @param  WriteAddr,写入地址
 * @param  NumByteToWrite,写入数据长度
 * @retval 无     //这个函数最好还是和I2C那个比一比
 */
void SPI_FLASH_BufferWrite(uint8_t * pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   //实现不定量的写入还是得调用上面的SPI_FLASH_PageWrite
{
    uint8_t NumOfPage=0;      //有几页
    uint8_t NumOfSingle=0;    //多出了几字节
    uint8_t Addr=0;           //???
    uint8_t count=0;          //
    uint8_t temp=0;

    /*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/
    Addr=WriteAddr%SPI_FLASH_PerWritePageSize;
    //差count个数据值,刚好可以对齐到页地址
    count=SPI_FLASH_PerWritePageSize-Addr;
    //计算出要写多少整数页
    NumOfPage=NumByteToWrite/SPI_FLASH_PerWritePageSize;
    //计算出不满一页的字节数
    NumOfSingle=NumByteToWrite%SPI_FLASH_PerWritePageSize;
    //和写EEPROM类似

    if(Addr==0)
    {   //要写的地址刚好对齐的情况
        if(NumOfPage==0)
        {   //NumByteToWrite<SPI_FLASH_PerWritePageSize
            SPI_FLASH_PageWrite(pBuffer,WriteAddr,NumByteToWrite);    //要写入的地址对齐到了页地址,且不足一页
        }
        else
        {   //对齐且超过一页
            //下面先把整数页给写了
            while(NumOfPage--)
            {
                SPI_FLASH_PageWrite(pBuffer,WriteAddr,SPI_FLASH_PerWritePageSize);
                //指针加的时候基类型不同,其移动的单位是不一样的
                pBuffer+=SPI_FLASH_PerWritePageSize;    //要写入数据的地址指针向后移动页个单位
                WriteAddr+=SPI_FLASH_PerWritePageSize;
            }
            //剩下的不满一页的数据要把它写完
            SPI_FLASH_PageWrite(pBuffer,WriteAddr,NumOfSingle);
        }
    }
    else    //若地址与SPI_FLASH不对齐
    {
        if(NumOfPage==0)    //且要写入的数据,不足一页
        {
            if(NumByteToWrite<=count)   //无法到顶
            {
                SPI_FLASH_PageWrite(pBuffer,WriteAddr,NumByteToWrite);
            }
            else     //要写的数据个数超过了count个
            {
                temp=NumByteToWrite-count;  //
                SPI_FLASH_PageWrite(pBuffer,WriteAddr,count);
                pBuffer+=count;
                WriteAddr+=count;
                //下面写完剩下的字节
                SPI_FLASH_PageWrite(pBuffer,WriteAddr,temp);
            }
        }
        else  //地址不对齐,且数据大于一页
        {
            //将地址不对齐的count个数据单独拿出来
            NumByteToWrite-=count;
            NumOfPage=NumByteToWrite/SPI_FLASH_PerWritePageSize;
            NumOfSingle=NumByteToWrite%SPI_FLASH_PerWritePageSize;
            //下面先写count个数据
            SPI_FLASH_PageWrite(pBuffer,WriteAddr,count);
            pBuffer+=count;
            WriteAddr+=count;
            //下面将整页的写入
            while(NumOfPage--)
            {
                SPI_FLASH_PageWrite(pBuffer,WriteAddr,SPI_FLASH_PerWritePageSize);
                pBuffer+=SPI_FLASH_PerWritePageSize;
                WriteAddr+=SPI_FLASH_PerWritePageSize;
            }
            if(NumOfSingle != 0)   //还有没写完的
            {
                SPI_FLASH_PageWrite(pBuffer,WriteAddr,NumOfSingle);
            }
        }
    }
}

/**
 *  @brief	读取FLASH数据
 *  @param  pBuffer,存储读取数据的指针(指向读取的数据)
 *  @param  ReadAddr,读取的地址
 *  @param  NumByteToRead,读取数据的长度
 *  @retval 无
 */
void SPI_FLASH_BufferRead(uint8_t *pBuffer,uint32_t ReadAddr,uint32_t NumByteToRead)
{
    //选择FLASH:CS低电平
    FLASH_SPI_CS_LOW();
    //发送读命令
    SPI_FLASH_SendByte(W25X_ReadData);
    //发送读的高字节
    SPI_FLASH_SendByte((ReadAddr&0x00FF0000)>>16);
    //发送中字节
    SPI_FLASH_SendByte((ReadAddr&0x0000FF00)>>8);
    //发送低字节
    SPI_FLASH_SendByte((ReadAddr&0x000000FF));
    //开始读取数据 (连续NumByteToRead个字节)
    while(NumByteToRead--)
    {
        *pBuffer++=SPI_FLASH_ReadByte();
    }
    //拉高CS引脚(即停止信号)
    FLASH_SPI_CS_HIGH();
}



